
#include <unistd.h>

#include <apr_strings.h>

#include "test_server.h"

#include "../utils/StringUtils.h"
#include "../net/HTTPServer.h"
#include "testHTTPServer.h"

/** Generate a HTML file from the pending database which acts as a security
 * console, only accessible from the local machine.
 * 
 * @param i
 * The thread/#pipearray number to serve it onto.
 * 
 * @param mp
 * The memory pool to use for allocation. */
void TestingHTTPServer::generateConsole(int i, apr_pool_t *mp)
{
   static const char *html_start = "<html><head><title>Console</title></head>" CRLF_STR;
   static const char *open_form = "<body><form name=\"permissions\" action=\"console.html\" method=\"POST\">" CRLF_STR;
   static const char *open_div = "<div align=\"center\"><br />" CRLF_STR;
   static const char *individ_header = "<h2>%s</h2>" CRLF_STR;
   static const char *user_record = "'%s' at %s from %s with %s &nbsp; " CRLF_STR;
   static const char *keyup_record = "'%s' expected host fingerprint '%s', obtained '%s' &nbsp; " CRLF_STR;
   static const char *input_radio = "<input type=\"radio\" name=\"%s\" value=\"%s\" />%s" CRLF_STR;
   static const char *input_cradio = "<input type=\"radio\" name=\"%s\" value=\"%s\" checked />%s" CRLF_STR;
   static const char *next_line = "<br />" CRLF_STR;
   static const char *submit_button = "<br /><br /><input type=\"submit\" action=\"console.html\" value=\"Submit Changes\" />" CRLF_STR;
   static const char *html_stop = "</div></form></body></html>" CRLF_STR CRLF_STR;
   static const char *firstheader = "HTTP/1.0 200 OK" CRLF_STR CRLF_STR;
   pipearray[i].handler_to_send->writeToStream((char *)firstheader, strlen(firstheader));
   pipearray[i].handler_to_send->writeToStream((char *)html_start, strlen(html_start));
   pipearray[i].handler_to_send->writeToStream((char *)open_form, strlen(open_form));
   pipearray[i].handler_to_send->writeToStream((char *)open_div, strlen(open_div));
   
   static const char *vars[] = { "mr_wibble", "mr_wobble", NULL };
   int count = 0;
   while (vars[count] != NULL)
   {
      pipearray[i].handler_to_send->writefToStream("%s &nbsp; ", vars[count]);
      pipearray[i].handler_to_send->writefToStream((char *)input_radio, vars[count], "Allow", "Allow");
      pipearray[i].handler_to_send->writefToStream((char *)input_radio, vars[count], "Deny", "Deny");
      pipearray[i].handler_to_send->writefToStream((char *)input_cradio, vars[count], "Ignore", "Ignore");
      pipearray[i].handler_to_send->writeToStream((char *)next_line, strlen(next_line));
      count++;
   }
   
   pipearray[i].handler_to_send->writeToStream((char *)submit_button, strlen(submit_button));
   pipearray[i].handler_to_send->writeToStream((char *)html_stop, strlen(html_stop));
   pipearray[i].handler_to_send->writeEOS();
}

/** Handle and reply to a HTTP GET session request.
 * 
 * @param i
 * The thread this function belongs to, and which pipe in the #pipearray
 * to use.
 * 
 * @param mp
 * The APR memory pool to use to allocate from. */
void TestingHTTPServer::handleGETSession(int i, apr_pool_t *mp)
{
   // download either own executable, hello! message, or faked console.
   static const char *fileswhitelist[] = { "/test_server", "/hello.html", "/console.html", NULL };
   APRTable *headertable = getHeaders(i, mp);
   if (headertable == NULL)
   {
      replyWithError404(i);
      errprint("Header table missing (out of memory?).");
      return;
   }
   if (headertable->isEmpty())
   {
      replyWithError404(i);
      errprint("Header table empty (unknown error).");
      delete headertable;
      return;
   }
   char *dstr = apr_pstrdup(mp, headertable->get("GET"));
   char *httpver = NULL;
   char *firsttok = apr_strtok(dstr, " ", &httpver);
   if (serveFilesFromWhiteList(headertable, i, mp, fileswhitelist)) { goto removeheadertable; }
   if (strcmp(firsttok, "/hello.html") == 0) { replyWithSentence(i, mp, "hello!"); goto removeheadertable; }
   if (strcmp(firsttok, "/console.html") == 0) { generateConsole(i, mp); goto removeheadertable; }
   replyWithError404(i);
removeheadertable:
   delete headertable;
}

/** Handle and reply to a HTTP POST session request.
 * 
 * @param i
 * The thread this function belongs to, and which pipe in the #pipearray
 * to use.
 *
 * @param mp
 * The APR memory pool to use to allocate from. */
void TestingHTTPServer::handlePOSTSession(int i, apr_pool_t *mp)
{
   if (strcmp(getClientAddressAsStaticString(i), "127.0.0.1") != 0)
   {
      errprint("erroneous post by %s", getClientAddressAsStaticString(i));
      pipearray[i].handler_to_send->writeEOS();
      return;
   }
   APRTable *headertable = getHeaders(i, mp);
   apr_off_t contentlength = getContentLength(headertable);
   APRTable *posttable = getPOSTData(i, mp, contentlength);
   std::vector<char *> *results = posttable->getAllAsStringList(" = ", "");
   for (int j = 0; j < results->size(); j++)
      printf("%s\n", results->at(j));
   redirectAfterPOST(i, mp, "/hello.html");
   StringUtils::destroyStringList(results);
   delete posttable;
   delete headertable;
}

/** Start handling clients to the server (the client handler threads run this).
 * 
 * @param tthread
 * The APR pointer to this thread grabbed.
 * 
 * @param args
 * The void pointer given to the thread. This is actually a pointer to an
 * array of two void pointers, which are the this pointer and a pointer to 
 * the thread ID respectively.
 * 
 * @return
 * Return is NULL. */
void *TestingHTTPServer::handleClientsOnThreadStatic(apr_thread_t *tthread, void *args)
{
   apr_status_t status;
   TestingHTTPServer *kthis = *((TestingHTTPServer **)(((void **)(args))[0]));
   int i = *((int *)(((void **)(args))[1]));
   apr_pool_t *upperpool, *mp;
   upperpool = apr_thread_pool_get(tthread);
   status = apr_pool_create(&mp, upperpool);
   CheckAPRError(status);
   
   kthis->handleClientsOnThread(i, mp);

   apr_pool_clear(mp);
   apr_pool_destroy(mp);
   ERR_remove_state(0);
   apr_thread_exit(tthread, APR_SUCCESS);
   return NULL;
}

/** The HTTP server in its default mode behaves as a file server from a
 * whitelist of file that it is allowed to serve. This can be tested by
 * instantiation and using a browser or other client to probe its behaviour.
 * This has been extended using a derived class to test its full set of
 * capabilities however. A console has been added with a form that can be
 * submitted via HTTP POST, sentence replies can be generated and the executable
 * can be downloaded.
 * 
 * @param hostname
 * Usually "0.0.0.0"
 * 
 * @param portno
 * Listening port.
 * 
 * @param mp
 * Memory pool to use. */
void tests::testHTTPServer(const char *hostname, int portno, apr_pool_t *mp)
{
   TestingHTTPServer *httpserv = new TestingHTTPServer(1, portno, hostname, mp);
   httpserv->startSessionThreads();
   httpserv->startListeningThread();
   while (!allstop)
   {
      sleep(1);
   }
   delete httpserv;
}
